/*
	Copyright 1999, Be Incorporated.   All Rights Reserved.
	This file may be used under the terms of the Be Sample Code License.
*/

/*	Parse the ASCII and raw versions of PPM.	*/

#include <Bitmap.h>
#include <BufferedDataIO.h>
#include <ByteOrder.h>
#include <Catalog.h>
#include <CheckBox.h>
#include <FindDirectory.h>
#include <LayoutBuilder.h>
#include <Locker.h>
#include <MenuField.h>
#include <MenuItem.h>
#include <Message.h>
#include <Path.h>
#include <PopUpMenu.h>
#include <Screen.h>
#include <StringView.h>
#include <TranslationKit.h>
#include <TranslatorAddOn.h>

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>

#include "colorspace.h"

#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "PPMTranslator"

#if DEBUG
#define dprintf(x) printf x
#else
#define dprintf(x)
#endif


#define PPM_TRANSLATOR_VERSION 0x100

/* These three data items are exported by every translator. */
char translatorName[] = "PPM images";
char translatorInfo[] = "PPM image translator v1.0.0, " __DATE__;
int32 translatorVersion = PPM_TRANSLATOR_VERSION;
// Revision: lowest 4 bits
// Minor: next 4 bits
// Major: highest 24 bits

/*	Be reserves all codes with non-lowercase letters in them.	*/
/*	Luckily, there is already a reserved code for PPM. If you	*/
/*	make up your own for a new type, use lower-case letters.	*/
#define PPM_TYPE 'PPM '


/* These two data arrays are a really good idea to export from Translators, but
 * not required. */
translation_format inputFormats[]
	= {{B_TRANSLATOR_BITMAP, B_TRANSLATOR_BITMAP, 0.4, 0.8, "image/x-be-bitmap",
		   "Be Bitmap Format (PPMTranslator)"},
		{PPM_TYPE, B_TRANSLATOR_BITMAP, 0.3, 0.8, "image/x-portable-pixmap",
			"PPM image"},
		{0, 0, 0, 0, "\0",
			"\0"}}; /*	optional (else Identify is always called)	*/

translation_format outputFormats[]
	= {{B_TRANSLATOR_BITMAP, B_TRANSLATOR_BITMAP, 0.4, 0.8, "image/x-be-bitmap",
		   "Be Bitmap Format (PPMTranslator)"},
		{PPM_TYPE, B_TRANSLATOR_BITMAP, 0.3, 0.8, "image/x-portable-pixmap",
			"PPM image"},
		{0, 0, 0, 0, "\0",
			"\0"}}; /*	optional (else Translate is called anyway)	*/

/*	Translators that don't export outputFormats 	*/
/*	will not be considered by files looking for 	*/
/*	specific output formats.	*/


/*	We keep our settings in a global struct, and wrap a lock around them.	*/
struct ppm_settings {
	color_space out_space;
	BPoint window_pos;
	bool write_ascii;
	bool settings_touched;
};
static BLocker g_settings_lock("PPM settings lock");
static ppm_settings g_settings;

BPoint get_window_origin();
void set_window_origin(BPoint pos);
BPoint
get_window_origin()
{
	BPoint ret;
	g_settings_lock.Lock();
	ret = g_settings.window_pos;
	g_settings_lock.Unlock();
	return ret;
}

void
set_window_origin(BPoint pos)
{
	g_settings_lock.Lock();
	g_settings.window_pos = pos;
	g_settings.settings_touched = true;
	g_settings_lock.Unlock();
}


class PrefsLoader
{
public:
	PrefsLoader(const char* str)
	{
		dprintf(("PPMTranslator: PrefsLoader()\n"));
		/* defaults */
		g_settings.out_space = B_NO_COLOR_SPACE;
		g_settings.window_pos = B_ORIGIN;
		g_settings.write_ascii = false;
		g_settings.settings_touched = false;
		BPath path;
		if (find_directory(B_USER_SETTINGS_DIRECTORY, &path))
			path.SetTo("/tmp");
		path.Append(str);
		FILE* f = fopen(path.Path(), "r");
		/*	parse text settings file -- this should be a library...	*/
		if (f) {
			char line[1024];
			char name[32];
			char* ptr;
			while (true) {
				line[0] = 0;
				fgets(line, 1024, f);
				if (!line[0])
					break;
				/* remember: line ends with \n, so printf()s don't have to */
				ptr = line;
				while (isspace(*ptr))
					ptr++;
				if (*ptr == '#' || !*ptr) /* comment or blank */
					continue;
				if (sscanf(ptr, "%31[a-zA-Z_0-9] =", name) != 1) {
					syslog(LOG_ERR,
						"unknown PPMTranslator "
						"settings line: %s",
						line);
				} else {
					if (!strcmp(name, "color_space")) {
						while (*ptr != '=')
							ptr++;
						ptr++;
						if (sscanf(ptr, "%d", (int*) &g_settings.out_space)
							!= 1) {
							syslog(LOG_ERR,
								"illegal color space "
								"in PPMTranslator settings: %s",
								ptr);
						}
					} else if (!strcmp(name, "window_pos")) {
						while (*ptr != '=')
							ptr++;
						ptr++;
						if (sscanf(ptr, "%f,%f", &g_settings.window_pos.x,
								&g_settings.window_pos.y)
							!= 2) {
							syslog(LOG_ERR,
								"illegal window position "
								"in PPMTranslator settings: %s",
								ptr);
						}
					} else if (!strcmp(name, "write_ascii")) {
						while (*ptr != '=')
							ptr++;
						ptr++;
						int ascii = g_settings.write_ascii;
						if (sscanf(ptr, "%d", &ascii) != 1) {
							syslog(LOG_ERR,
								"illegal write_ascii value "
								"in PPMTranslator settings: %s",
								ptr);
						} else {
							g_settings.write_ascii = ascii;
						}
					} else {
						syslog(
							LOG_ERR, "unknown PPMTranslator setting: %s", line);
					}
				}
			}
			fclose(f);
		}
	}

	~PrefsLoader()
	{
		/*	No need writing settings if there aren't any	*/
		if (g_settings.settings_touched) {
			BPath path;
			if (find_directory(B_USER_SETTINGS_DIRECTORY, &path))
				path.SetTo("/tmp");
			path.Append("PPMTranslator_Settings");
			FILE* f = fopen(path.Path(), "w");
			if (f) {
				fprintf(f, "# PPMTranslator settings version %d.%d.%d\n",
					static_cast<int>(translatorVersion >> 8),
					static_cast<int>((translatorVersion >> 4) & 0xf),
					static_cast<int>(translatorVersion & 0xf));
				fprintf(f, "color_space = %d\n", g_settings.out_space);
				fprintf(f, "window_pos = %g,%g\n", g_settings.window_pos.x,
					g_settings.window_pos.y);
				fprintf(
					f, "write_ascii = %d\n", g_settings.write_ascii ? 1 : 0);
				fclose(f);
			}
		}
	}
};

PrefsLoader g_prefs_loader("PPMTranslator_Settings");

/*	Some prototypes for functions we use.	*/
status_t read_ppm_header(BDataIO* io, int* width, int* rowbytes, int* height,
	int* max, bool* ascii, color_space* space, bool* is_ppm, char** comment);
status_t read_bits_header(BDataIO* io, int skipped, int* width, int* rowbytes,
	int* height, int* max, bool* ascii, color_space* space);
status_t write_comment(const char* str, BDataIO* io);
status_t copy_data(BDataIO* in, BDataIO* out, int rowbytes, int out_rowbytes,
	int height, int max, bool in_ascii, bool out_ascii, color_space in_space,
	color_space out_space);


/*	Return B_NO_TRANSLATOR if not handling this data.	*/
/*	Even if inputFormats exists, may be called for data without hints	*/
/*	If outType is not 0, must be able to export in wanted format	*/
status_t
Identify(BPositionIO* inSource, const translation_format* inFormat,
	BMessage* ioExtension, translator_info* outInfo, uint32 outType)
{
	dprintf(("PPMTranslator: Identify()\n"));
	/* Silence compiler warnings. */
	(void)inFormat;
	(void)ioExtension;

	/* Check that requested format is something we can deal with. */
	if (outType == 0)
		outType = B_TRANSLATOR_BITMAP;

	if (outType != B_TRANSLATOR_BITMAP && outType != PPM_TYPE)
		return B_NO_TRANSLATOR;

	/* Check header. */
	int width, rowbytes, height, max;
	bool ascii, is_ppm;
	color_space space;
	status_t err = read_ppm_header(inSource, &width, &rowbytes, &height, &max,
		&ascii, &space, &is_ppm, NULL);
	if (err != B_OK)
		return err;

	/* Stuff info into info struct -- Translation Kit will do "translator" for
	 * us. */
	outInfo->group = B_TRANSLATOR_BITMAP;
	if (is_ppm) {
		outInfo->type = PPM_TYPE;
		outInfo->quality = 0.3; /* no alpha, etc */
		outInfo->capability
			= 0.8; /* we're pretty good at PPM reading, though */
		strlcpy(outInfo->name, B_TRANSLATE("PPM image"), sizeof(outInfo->name));
		strcpy(outInfo->MIME, "image/x-portable-pixmap");
	} else {
		outInfo->type = B_TRANSLATOR_BITMAP;
		outInfo->quality = 0.4; /* B_TRANSLATOR_BITMAP can do alpha, at least */
		outInfo->capability
			= 0.8; /* and we might not know many variations thereof */
		strlcpy(outInfo->name, B_TRANSLATE("Be Bitmap Format (PPMTranslator)"),
			sizeof(outInfo->name));
		strcpy(outInfo->MIME, "image/x-be-bitmap"); /* this is the MIME type of
													   B_TRANSLATOR_BITMAP */
	}
	return B_OK;
}


/*	Return B_NO_TRANSLATOR if not handling the output format	*/
/*	If outputFormats exists, will only be called for those formats	*/
status_t
Translate(BPositionIO* inSource, const translator_info* /*inInfo*/,
	BMessage* ioExtension, uint32 outType, BPositionIO* outDestination)
{
	dprintf(("PPMTranslator: Translate()\n"));
	inSource->Seek(0, SEEK_SET); /* paranoia */
	//	inInfo = inInfo;	/* silence compiler warning */
	/* Check what we're being asked to produce. */
	if (!outType)
		outType = B_TRANSLATOR_BITMAP;
	dprintf(("PPMTranslator: outType is '%c%c%c%c'\n", char(outType >> 24),
		char(outType >> 16), char(outType >> 8), char(outType)));
	if (outType != B_TRANSLATOR_BITMAP && outType != PPM_TYPE)
		return B_NO_TRANSLATOR;

	/* Figure out what we've been given (again). */
	int width, rowbytes, height, max;
	bool ascii, is_ppm;
	color_space space;
	/* Read_ppm_header() will always return with stream at beginning of data */
	/* for both B_TRANSLATOR_BITMAP and PPM_TYPE, and indicate what it is. */
	char* comment = NULL;
	status_t err = read_ppm_header(inSource, &width, &rowbytes, &height, &max,
		&ascii, &space, &is_ppm, &comment);
	if (comment != NULL) {
		if (ioExtension != NULL)
			ioExtension->AddString(B_TRANSLATOR_EXT_COMMENT, comment);
		free(comment);
	}
	if (err < B_OK) {
		dprintf(("read_ppm_header() error %s [%" B_PRIx32 "]\n", strerror(err),
			err));
		return err;
	}
	/* Check if we're configured to write ASCII format file. */
	bool out_ascii = false;
	if (outType == PPM_TYPE) {
		out_ascii = g_settings.write_ascii;
		if (ioExtension != NULL)
			ioExtension->FindBool("ppm /ascii", &out_ascii);
	}
	err = B_OK;
	/* Figure out which color space to convert to */
	color_space out_space;
	int out_rowbytes;
	g_settings_lock.Lock();
	if (outType == PPM_TYPE) {
		out_space = B_RGB24_BIG;
		out_rowbytes = 3 * width;
	} else { /*	When outputting to B_TRANSLATOR_BITMAP, follow user's wishes.
			  */
		if (!ioExtension
			|| ioExtension->FindInt32( B_TRANSLATOR_EXT_BITMAP_COLOR_SPACE,
				(int32*) &out_space)
			|| (out_space == B_NO_COLOR_SPACE)) {
			if (g_settings.out_space == B_NO_COLOR_SPACE) {
				switch (space) { /*	The 24-bit versions are pretty silly, use 32
									instead.	*/
					case B_RGB24:
					case B_RGB24_BIG:
						out_space = B_RGB32;
						break;
					default:
						/* use whatever is there */
						out_space = space;
						break;
				}
			} else {
				out_space = g_settings.out_space;
			}
		}
		out_rowbytes = calc_rowbytes(out_space, width);
	}
	g_settings_lock.Unlock();
	/* Write file header */
	if (outType == PPM_TYPE) {
		dprintf(("PPMTranslator: write PPM\n"));
		/* begin PPM header */
		const char* magic;
		if (out_ascii)
			magic = "P3\n";
		else
			magic = "P6\n";
		err = outDestination->Write(magic, strlen(magic));
		if (err == (long) strlen(magic))
			err = 0;
		// comment = NULL;
		const char* fsComment;
		if ((ioExtension != NULL)
			&& !ioExtension->FindString(B_TRANSLATOR_EXT_COMMENT, &fsComment))
			err = write_comment(fsComment, outDestination);
		if (err == B_OK) {
			char data[40];
			sprintf(data, "%d %d %d\n", width, height, max);
			err = outDestination->Write(data, strlen(data));
			if (err == (long) strlen(data))
				err = 0;
		}
		/* header done */
	} else {
		dprintf(("PPMTranslator: write B_TRANSLATOR_BITMAP\n"));
		/* B_TRANSLATOR_BITMAP header */
		TranslatorBitmap hdr;
		hdr.magic = B_HOST_TO_BENDIAN_INT32(B_TRANSLATOR_BITMAP);
		hdr.bounds.left = B_HOST_TO_BENDIAN_FLOAT(0);
		hdr.bounds.top = B_HOST_TO_BENDIAN_FLOAT(0);
		hdr.bounds.right = B_HOST_TO_BENDIAN_FLOAT(width - 1);
		hdr.bounds.bottom = B_HOST_TO_BENDIAN_FLOAT(height - 1);
		hdr.rowBytes = B_HOST_TO_BENDIAN_INT32(out_rowbytes);
		hdr.colors = (color_space) B_HOST_TO_BENDIAN_INT32(out_space);
		hdr.dataSize = B_HOST_TO_BENDIAN_INT32(out_rowbytes * height);
		dprintf(("rowBytes is %d, width %d, out_space %x, space %x\n",
			out_rowbytes, width, out_space, space));
		err = outDestination->Write(&hdr, sizeof(hdr));
		dprintf(("PPMTranslator: Write() returns %" B_PRIx32 "\n", err));
#if DEBUG
		{
			BBitmap* map
				= new BBitmap(BRect(0, 0, width - 1, height - 1), out_space);
			printf("map rb = %" B_PRId32 "\n", map->BytesPerRow());
			delete map;
		}
#endif
		if (err == sizeof(hdr))
			err = 0;
		/* header done */
	}
	if (err != B_OK)
		return err > 0 ? B_IO_ERROR : err;
	/* Write data. Luckily, PPM and B_TRANSLATOR_BITMAP both scan from left to
	 * right, top to bottom. */
	return copy_data(inSource, outDestination, rowbytes, out_rowbytes, height,
		max, ascii, out_ascii, space, out_space);
}


class PPMView : public BView
{
public:
	PPMView(const char* name, uint32 flags) : BView(name, flags)
	{
		SetViewUIColor(B_PANEL_BACKGROUND_COLOR);

		fTitle = new BStringView("title", B_TRANSLATE("PPM image translator"));
		fTitle->SetFont(be_bold_font);

		char detail[100];
		int ver = static_cast<int>(translatorVersion);
		sprintf(detail, B_TRANSLATE("Version %d.%d.%d, %s"), ver >> 8,
			((ver >> 4) & 0xf), (ver & 0xf), __DATE__);
		fDetail = new BStringView("detail", detail);

		fBasedOn = new BStringView(
			"basedOn", B_TRANSLATE("Based on PPMTranslator sample code"));

		fCopyright = new BStringView("copyright",
			B_TRANSLATE("Sample code copyright 1999, Be Incorporated"));

		fMenu = new BPopUpMenu("Color Space");
		fMenu->AddItem(
			new BMenuItem(B_TRANSLATE("None"), CSMessage(B_NO_COLOR_SPACE)));
		fMenu->AddItem(new BMenuItem(
			B_TRANSLATE("RGB 8:8:8 32 bits"), CSMessage(B_RGB32)));
		fMenu->AddItem(new BMenuItem(B_TRANSLATE("RGBA 8:8:8:8 32 "
												 "bits"),
			CSMessage(B_RGBA32)));
		fMenu->AddItem(new BMenuItem(
			B_TRANSLATE("RGB 5:5:5 16 bits"), CSMessage(B_RGB15)));
		fMenu->AddItem(new BMenuItem(B_TRANSLATE("RGBA 5:5:5:1 16 "
												 "bits"),
			CSMessage(B_RGBA15)));
		fMenu->AddItem(new BMenuItem(
			B_TRANSLATE("RGB 5:6:5 16 bits"), CSMessage(B_RGB16)));
		fMenu->AddItem(new BMenuItem(B_TRANSLATE("System palette 8 "
												 "bits"),
			CSMessage(B_CMAP8)));
		fMenu->AddSeparatorItem();
		fMenu->AddItem(
			new BMenuItem(B_TRANSLATE("Grayscale 8 bits"), CSMessage(B_GRAY8)));
		fMenu->AddItem(
			new BMenuItem(B_TRANSLATE("Bitmap 1 bit"), CSMessage(B_GRAY1)));
		fMenu->AddItem(new BMenuItem(
			B_TRANSLATE("CMY 8:8:8 32 bits"), CSMessage(B_CMY32)));
		fMenu->AddItem(new BMenuItem(B_TRANSLATE("CMYA 8:8:8:8 32 "
												 "bits"),
			CSMessage(B_CMYA32)));
		fMenu->AddItem(new BMenuItem(B_TRANSLATE("CMYK 8:8:8:8 32 "
												 "bits"),
			CSMessage(B_CMYK32)));
		fMenu->AddSeparatorItem();
		fMenu->AddItem(new BMenuItem(B_TRANSLATE("RGB 8:8:8 32 bits "
												 "big-endian"),
			CSMessage(B_RGB32_BIG)));
		fMenu->AddItem(new BMenuItem(B_TRANSLATE("RGBA 8:8:8:8 32 "
												 "bits big-endian"),
			CSMessage(B_RGBA32_BIG)));
		fMenu->AddItem(new BMenuItem(B_TRANSLATE("RGB 5:5:5 16 bits "
												 "big-endian"),
			CSMessage(B_RGB15_BIG)));
		fMenu->AddItem(new BMenuItem(B_TRANSLATE("RGBA 5:5:5:1 16 "
												 "bits big-endian"),
			CSMessage(B_RGBA15_BIG)));
		fMenu->AddItem(new BMenuItem(B_TRANSLATE("RGB 5:6:5 16 bits "
												 "big-endian"),
			CSMessage(B_RGB16)));
		fField = new BMenuField(B_TRANSLATE("Input color space:"), fMenu);
		fField->SetViewColor(ViewColor());
		SelectColorSpace(g_settings.out_space);
		BMessage* msg = new BMessage(CHANGE_ASCII);
		fAscii = new BCheckBox(B_TRANSLATE("Write ASCII"), msg);
		if (g_settings.write_ascii)
			fAscii->SetValue(1);
		fAscii->SetViewColor(ViewColor());

		// Build the layout
		BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
			.SetInsets(B_USE_DEFAULT_SPACING)
			.Add(fTitle)
			.Add(fDetail)
			.AddGlue()
			.AddGroup(B_HORIZONTAL)
			.Add(fField)
			.AddGlue()
			.End()
			.Add(fAscii)
			.AddGlue()
			.Add(fBasedOn)
			.Add(fCopyright);
	}
	~PPMView() { /* nothing here */ }

	enum { SET_COLOR_SPACE = 'ppm=', CHANGE_ASCII };

	virtual void MessageReceived(BMessage* message)
	{
		if (message->what == SET_COLOR_SPACE) {
			SetSettings(message);
		} else if (message->what == CHANGE_ASCII) {
			BMessage msg;
			msg.AddBool("ppm /ascii", fAscii->Value());
			SetSettings(&msg);
		} else {
			BView::MessageReceived(message);
		}
	}
	virtual void AllAttached()
	{
		BView::AllAttached();
		BMessenger msgr(this);
		/*	Tell all menu items we're the man.	*/
		for (int ix = 0; ix < fMenu->CountItems(); ix++) {
			BMenuItem* i = fMenu->ItemAt(ix);
			if (i)
				i->SetTarget(msgr);
		}
		fAscii->SetTarget(msgr);
	}

	void SetSettings(BMessage* message)
	{
		g_settings_lock.Lock();
		color_space space;
		if (!message->FindInt32("space", (int32*) &space)) {
			g_settings.out_space = space;
			SelectColorSpace(space);
			g_settings.settings_touched = true;
		}
		bool ascii;
		if (!message->FindBool("ppm /ascii", &ascii)) {
			g_settings.write_ascii = ascii;
			g_settings.settings_touched = true;
		}
		g_settings_lock.Unlock();
	}

private:
	BStringView* fTitle;
	BStringView* fDetail;
	BStringView* fBasedOn;
	BStringView* fCopyright;
	BPopUpMenu* fMenu;
	BMenuField* fField;
	BCheckBox* fAscii;

	BMessage* CSMessage(color_space space)
	{
		BMessage* ret = new BMessage(SET_COLOR_SPACE);
		ret->AddInt32("space", space);
		return ret;
	}

	void SelectColorSpace(color_space space)
	{
		for (int ix = 0; ix < fMenu->CountItems(); ix++) {
			int32 s;
			BMenuItem* i = fMenu->ItemAt(ix);
			if (i) {
				BMessage* m = i->Message();
				if (m && !m->FindInt32("space", &s) && (s == space)) {
					fMenu->Superitem()->SetLabel(i->Label());
					break;
				}
			}
		}
	}
};


/*	The view will get resized to what the parent thinks is 	*/
/*	reasonable. However, it will still receive MouseDowns etc.	*/
/*	Your view should change settings in the translator immediately, 	*/
/*	taking care not to change parameters for a translation that is 	*/
/*	currently running. Typically, you'll have a global struct for 	*/
/*	settings that is atomically copied into the translator function 	*/
/*	as a local when translation starts.	*/
/*	Store your settings wherever you feel like it.	*/
status_t
MakeConfig(BMessage* ioExtension, BView** outView, BRect* outExtent)
{
	PPMView* v
		= new PPMView(B_TRANSLATE("PPMTranslator Settings"), B_WILL_DRAW);
	*outView = v;
	v->ResizeTo(v->ExplicitPreferredSize());
	;
	*outExtent = v->Bounds();
	if (ioExtension)
		v->SetSettings(ioExtension);
	return B_OK;
}


/*	Copy your current settings to a BMessage that may be passed 	*/
/*	to BTranslators::Translate at some later time when the user wants to 	*/
/*	use whatever settings you're using right now.	*/
status_t
GetConfigMessage(BMessage* ioExtension)
{
	status_t err = B_OK;
	const char* name = B_TRANSLATOR_EXT_BITMAP_COLOR_SPACE;
	g_settings_lock.Lock();
	(void) ioExtension->RemoveName(name);
	err = ioExtension->AddInt32(name, g_settings.out_space);
	g_settings_lock.Unlock();
	return err;
}


status_t
read_ppm_header(BDataIO* inSource, int* width, int* rowbytes, int* height,
	int* max, bool* ascii, color_space* space, bool* is_ppm, char** comment)
{
	/* check for PPM magic number */
	char ch[2];
	bool monochrome = false;
	bool greyscale = false;
	if (inSource->Read(ch, 2) != 2)
		return B_NO_TRANSLATOR;
	/* look for magic number */
	if (ch[0] != 'P') {
		/* B_TRANSLATOR_BITMAP magic? */
		if (ch[0] == 'b' && ch[1] == 'i') {
			*is_ppm = false;
			return read_bits_header(
				inSource, 2, width, rowbytes, height, max, ascii, space);
		}
		return B_NO_TRANSLATOR;
	}
	*is_ppm = true;
	if (ch[1] == '6' || ch[1] == '5' || ch[1] == '4') {
		*ascii = false;
	} else if (ch[1] == '3' || ch[1] == '2' || ch[1] == '1') {
		*ascii = true;
	} else {
		return B_NO_TRANSLATOR;
	}

	if (ch[1] == '4' || ch[1] == '1')
		monochrome = true;
	else if (ch[1] == '5' || ch[1] == '2')
		greyscale = true;

	// status_t err = B_NO_TRANSLATOR;
	enum scan_state {
		scan_width,
		scan_height,
		scan_max,
		scan_white
	} state
		= scan_width;
	int* scan = NULL;
	bool in_comment = false;
	if (monochrome)
		*space = B_GRAY1;
	else if (greyscale)
		*space = B_GRAY8;
	else
		*space = B_RGB24_BIG;
	/* The description of PPM is slightly ambiguous as far as comments */
	/* go. We choose to allow comments anywhere, in the spirit of laxness. */
	/* See http://www.dcs.ed.ac.uk/~mxr/gfx/2d/PPM.txt */
	int comlen = 0;
	int comptr = 0;
	while (inSource->Read(ch, 1) == 1) {
		if (in_comment && (ch[0] != 10) && (ch[0] != 13)) {
			if (comment) { /* collect comment(s) into comment string */
				if (comptr >= comlen - 1) {
					char* n = (char*) realloc(*comment, comlen + 100);
					if (!n) {
						free(*comment);
						*comment = NULL;
					}
					*comment = n;
					comlen += 100;
				}
				(*comment)[comptr++] = ch[0];
				(*comment)[comptr] = 0;
			}
			continue;
		}
		in_comment = false;
		if (ch[0] == '#') {
			in_comment = true;
			continue;
		}
		/* once we're done with whitespace after max, we're done with header */
		if (isdigit(ch[0])) {
			if (!scan) { /* first digit for this value */
				switch (state) {
					case scan_width:
						scan = width;
						break;
					case scan_height:
						if (monochrome)
							*rowbytes = (*width + 7) / 8;
						else if (greyscale)
							*rowbytes = *width;
						else
							*rowbytes = *width * 3;
						scan = height;
						break;
					case scan_max:
						scan = max;
						break;
					default:
						return B_OK; /* done with header, all OK */
				}
				*scan = 0;
			}
			*scan = (*scan) * 10 + (ch[0] - '0');
		} else if (isspace(ch[0])) {
			if (scan) { /* are we done with one value? */
				scan = NULL;
				state = (enum scan_state)(state + 1);

				/* in monochrome ppm, there is no max in the file, so we
				 * skip that step. */
				if ((state == scan_max) && monochrome) {
					state = (enum scan_state)(state + 1);
					*max = 1;
				}
			}

			if (state == scan_white) { /* we only ever read one whitespace,
										  since we skip space */
				return B_OK; /* when reading ASCII, and there is a single
								whitespace after max in raw mode */
			}
		} else {
			if (state != scan_white)
				return B_NO_TRANSLATOR;
			return B_OK; /* header done */
		}
	}
	return B_NO_TRANSLATOR;
}


status_t
read_bits_header(BDataIO* io, int skipped, int* width, int* rowbytes,
	int* height, int* max, bool* ascii, color_space* space)
{
	/* read the rest of a possible B_TRANSLATOR_BITMAP header */
	if (skipped < 0 || skipped > 4)
		return B_NO_TRANSLATOR;
	int rd = sizeof(TranslatorBitmap) - skipped;
	TranslatorBitmap hdr;
	/* pre-initialize magic because we might have skipped part of it already */
	hdr.magic = B_HOST_TO_BENDIAN_INT32(B_TRANSLATOR_BITMAP);
	char* ptr = (char*) &hdr;
	if (io->Read(ptr + skipped, rd) != rd)
		return B_NO_TRANSLATOR;
	/* swap header values */
	hdr.magic = B_BENDIAN_TO_HOST_INT32(hdr.magic);
	hdr.bounds.left = B_BENDIAN_TO_HOST_FLOAT(hdr.bounds.left);
	hdr.bounds.right = B_BENDIAN_TO_HOST_FLOAT(hdr.bounds.right);
	hdr.bounds.top = B_BENDIAN_TO_HOST_FLOAT(hdr.bounds.top);
	hdr.bounds.bottom = B_BENDIAN_TO_HOST_FLOAT(hdr.bounds.bottom);
	hdr.rowBytes = B_BENDIAN_TO_HOST_INT32(hdr.rowBytes);
	hdr.colors = (color_space) B_BENDIAN_TO_HOST_INT32(hdr.colors);
	hdr.dataSize = B_BENDIAN_TO_HOST_INT32(hdr.dataSize);
	/* sanity checking */
	if (hdr.magic != B_TRANSLATOR_BITMAP)
		return B_NO_TRANSLATOR;
	if (hdr.colors & 0xffff0000) { /* according to <GraphicsDefs.h> this is a
									  reasonable check. */
		return B_NO_TRANSLATOR;
	}
	if (hdr.rowBytes * (hdr.bounds.Height() + 1) > hdr.dataSize)
		return B_NO_TRANSLATOR;
	/* return information about the data in the stream */
	*width = (int) hdr.bounds.Width() + 1;
	*rowbytes = hdr.rowBytes;
	*height = (int) hdr.bounds.Height() + 1;
	*max = 255;
	*ascii = false;
	*space = hdr.colors;
	return B_OK;
}


/*	String may contain newlines, after which we need to insert extra hash signs.
 */
status_t
write_comment(const char* str, BDataIO* io)
{
	const char* end = str + strlen(str);
	const char* ptr = str;
	status_t err = B_OK;
	/* write each line as it's found */
	while ((ptr < end) && !err) {
		if ((*ptr == 10) || (*ptr == 13)) {
			err = io->Write("# ", 2);
			if (err == 2) {
				err = io->Write(str, ptr - str);
				if (err == ptr - str) {
					if (io->Write("\n", 1) == 1)
						err = 0;
				}
			}
			str = ptr + 1;
		}
		ptr++;
	}
	/* write the last data, if any, as a line */
	if (ptr > str) {
		err = io->Write("# ", 2);
		if (err == 2) {
			err = io->Write(str, ptr - str);
			if (err == ptr - str) {
				if (io->Write("\n", 1) == 1)
					err = 0;
			}
		}
	}
	if (err > 0)
		err = B_IO_ERROR;
	return err;
}


static status_t
read_ascii_line(BDataIO* in, int max, unsigned char* data, int rowbytes)
{
	char ch;
	status_t err;
	// int nread = 0;
	bool comment = false;
	int val = 0;
	bool dig = false;
	while ((err = in->Read(&ch, 1)) == 1) {
		if (comment) {
			if ((ch == '\n') || (ch == '\r'))
				comment = false;
		}
		if (isdigit(ch)) {
			dig = true;
			val = val * 10 + (ch - '0');
		} else {
			if (dig) {
				*(data++) = val * 255 / max;
				val = 0;
				rowbytes--;
				dig = false;
			}
			if (ch == '#') {
				comment = true;
				continue;
			}
		}
		if (rowbytes < 1)
			break;
	}
	if (dig) {
		*(data++) = val * 255 / max;
		val = 0;
		rowbytes--;
		dig = false;
	}
	if (rowbytes < 1)
		return B_OK;
	return B_IO_ERROR;
}


static status_t
write_ascii_line(BDataIO* out, unsigned char* data, int rowbytes)
{
	char buffer[20];
	int linelen = 0;
	while (rowbytes > 2) {
		sprintf(buffer, "%d %d %d ", data[0], data[1], data[2]);
		rowbytes -= 3;
		int l = strlen(buffer);
		if (l + linelen > 70) {
			out->Write("\n", 1);
			linelen = 0;
		}
		if (out->Write(buffer, l) != l)
			return B_IO_ERROR;
		linelen += l;
		data += 3;
	}
	out->Write("\n", 1); /* terminate each scanline */
	return B_OK;
}


static unsigned char*
make_scale_data(int max)
{
	unsigned char* ptr = (unsigned char*) malloc(max);
	for (int ix = 0; ix < max; ix++)
		ptr[ix] = ix * 255 / max;
	return ptr;
}


static void
scale_data(unsigned char* scale, unsigned char* data, int bytes)
{
	for (int ix = 0; ix < bytes; ix++)
		data[ix] = scale[data[ix]];
}


status_t
copy_data(BDataIO* in, BDataIO* out, int rowbytes, int out_rowbytes, int height,
	int max, bool in_ascii, bool out_ascii, color_space in_space,
	color_space out_space)
{
	dprintf(("copy_data(%d, %d, %d, %d, %x, %x)\n", rowbytes, out_rowbytes,
		height, max, in_space, out_space));

	// We will be reading 1 char at a time, so use a buffer
	BBufferedDataIO inBuffer(*in, 65536, false);

	/*	We read/write one scanline at a time.	*/
	unsigned char* data = (unsigned char*) malloc(rowbytes);
	unsigned char* out_data = (unsigned char*) malloc(out_rowbytes);
	if (data == NULL || out_data == NULL) {
		free(data);
		free(out_data);
		return B_NO_MEMORY;
	}
	unsigned char* scale = NULL;
	if (max != 255 && in_space != B_GRAY1)
		scale = make_scale_data(max);
	status_t err = B_OK;
	/*	There is no data format conversion, so we can just copy data.	*/
	while ((height-- > 0) && !err) {
		if (in_ascii) {
			err = read_ascii_line(&inBuffer, max, data, rowbytes);
		} else {
			err = inBuffer.Read(data, rowbytes);
			if (err == rowbytes)
				err = B_OK;
			if (scale) /* for reading PPM that is smaller than 8 bit */
				scale_data(scale, data, rowbytes);
		}
		if (err == B_OK) {
			unsigned char* wbuf = data;
			if (in_space != out_space) {
				err = convert_space(
					in_space, out_space, data, rowbytes, out_data);
				wbuf = out_data;
			}
			if (!err && out_ascii) {
				err = write_ascii_line(out, wbuf, out_rowbytes);
			} else if (!err) {
				err = out->Write(wbuf, out_rowbytes);
				if (err == out_rowbytes)
					err = B_OK;
			}
		}
	}
	free(data);
	free(out_data);
	free(scale);
	return err > 0 ? B_IO_ERROR : err;
}
